今天的主題在 Hackerrank 的安排下是延續昨天的主題。昨天我們做的事情是當發現異常的時候,各種語言是用什麼樣的方式在解決。然而今天我們要來看看如何讓我們自己的程式能夠拋出異常來讓其他程式做後續處理囉!今天也會更深入地探討這四個語言在異常處理還有什麼有趣之處。那就讓我們開始吧!
class Calculator(object):
def power(self, n, p):
if (n < 0) or (p < 0):
raise Exception("n and p should be non-negative")
else:
return n ** p
myCalculator = Calculator()
time = int(input())
for i in range(time):
n, p = map(int, input().split())
try:
ans = myCalculator.power(n, p)
print(ans)
except Exception as e:
print(e)
Calculator
,裏頭有一個 Method power
是用來計算 n
的 p
次方的值是多少。然而我們並不希望 n
或 p
其中有人是負的,如果有人是負的,那麼我們就會試圖拋出一個 Exception,反之則返回正確的值。我們可以看到 raise Exception("n and p should be non-negative")
就是在拋出異常囉!這裡的關鍵字是 raise
。這裡我們可以做出自己的 Exception,只要繼承自 BaseException
就可以了。像是常見的 KeyboardInterrupt
也是繼承自 BaseException
,也就是當我們想要中斷程式有時候會按 ctrl-c
,系統就會拋出這個異常囉!然而如果我們今天只是用 try...except
,而沒有特別指明要比對的 Exception 的類型,那麼在例如下面的程式,一個無窮迴圈內含一個可以捕捉全部 Exception 的 try...except
就無法透過 KeyboardInterrupt
,通常是 ctrl-c
,來中斷程式了。while True:
try:
...
except:
...
map(int, input().split())
的意思是將第二個參數 input().split()
,也就是 User 輸入的字串做分割後得到的 List,其中每一個元素都套用第一個參數,也就是 int
這個 Function,來得到一個新的 List,跟 Scala 的 map
是一樣的效果。最後看到 except Exception as e
,這裡當 Exception 被捕獲的時候,將其賦予給 e
,供 except
的程式區塊去使用囉!在這裡就是簡單地將 e
給印出來,也就是 n and p should be non-negative
。throw
,例如當程式啟動時,參數不符合預期,我們可以 throw IllegalArgumentException
。IllegalArgumentException
繼承了 Exception
這個 class,所以如果我們要客製出一個自己的 Exception 就要使之繼承 Exception
。例如下面,其中 e
是該 Exception 的文字解釋,可以在 Match exception 的時候將 e
印出。class CustomException(s: String) extends Exception(s)
Try
這個類型。記得我們講過 Option
嗎?可能有值也可能沒值的時候就用 Option
。Try
是一樣的道理:可能會有異常也可能成功地拿到結果。Try[A]
(A 是 Type parameter,也就是泛型,之後會提到),表示結果的類型是 A
,例如 Int
,而 Try[A]
有兩個子類型 Success[A]
和封裝了 Exception 的 Failure[A]
。所以假使我們知道函式可能會有異常的結果,我們就讓返回值是 Try
類型,這樣子呼叫者就知道必須要處理可能異常的狀況了!例如下面的 parseInt
,我們用了 Try
的 apply
Method (記得語法糖嗎),把可能會產生異常的計算當作參數,假如今天計算有了異常,這個 apply
Method 就會捕獲異常,並返回 Failure[Int]
。至於呼叫者拿到返回結果後,最常見的方式就是透過 getOrElse
這個 Try
的 Method 來得到成功的結果或是當失敗的時候拿到一個預設值,也就是下面 getOrElse
後面的 0
。當然你也可以用 pattern matching 的方式,或把 Try
當成像是 Option
或其他 Collection 的類型去做後續處理,像是 map
,flatMap
之類等等。可以參考這裡囉!def parseInt(s: String): Try[Int] = Try(Integer.parseInt(s.trim))
val i = parseInt("xxx") getOrElse 0
val2, _ := myFunction(...)
的 _
,但不建議忽略。另外在 Golang 也可以使用 panic
來讓程式遇到無法恢復的錯誤時將程式中斷,一般不建議直接使用。val1, err := myFunction(...)
if err != nil {
// handle error
}
val2, _ := myFunction(...)
error
這個 interface 裡面有一個 Error() string
的函式簽名,只要一個 Struct 有實作 Error() string
我們就說這個 Struct 可以是個 error
。常見的例如 Built-in 的 errors.New(<error message>)
就會返回 errorString
Struct 的 Pointer,而這個 Struct 的 Pointer 也實作了 Error() string
這個函式。fmt.Errorf(<error message>)
也是。type error interface {
Error() string
}
error
的 Struct 就可以當作 error
來返回,但是如果我們想知道究竟這個 error
是哪個 Type 的 Struct 呢?就要用到 Type assertion,例如下面的程式,定義了兩個 Type NetworkError
和 FileError
,且都實作了 error
interface。而執行 val, err := MyFunction()
後得到的 err
可能是 nil
, *NetworkError
或 *FileError
。透過 err, ok := err.(*NetworkError)
,假設 err
是*NetworkError
,ok
的值就會是 true,就可以後續把 error
當作是 *NetworkError
來存取。如果我們想要根據不同的 Type 去做對應的事,就可以透過去 switch err.(type)
來進行,如同下面的例子囉! 更多關於 Golang 如何做 Error Handling 可以參考這裡。type NetworkError struct { n string }
type FileError struct { f string }
func (networkError *NetworkError) Error() string { ... }
func (fileError *FileError) Error() string { ... }
val, err := MyFunction() // may return *NetworkError or *FileError
err, ok := err.(*NetworkError)
if ok {
fmt.Println(err.n)
}
switch _, err := MyFunction(); err.(type) {
case nil:
...
case *NetworkError:
...
case *FileError:
...
}
panic!()
這個 Macro (panic!()
是 thread-based,所以在多 Thread 時,其中一個 Thread 被 Panic 時,其他還是可以繼續運行),使用上 panic!(<some message>);
。還有一些 Macro 也可以有 panic!()
的效果,但是有更具體的語意,像是 unimplemented!()
,表示這一部分還沒有實作 (會 show not yet implemented
);unreachable!()
可以表示某段 Code 不應該進入 (會 show internal error: entered unreachable code
);還有像是 assert!()
等等。Option
(Some
或 None
) 或 Result
(Ok
或 Err
)。Option
是在當函式可以回傳空值時使用 (假使一個函式的參數是 Optional 也可以用)。Result
則是當函式可以回傳 Error 的情況下去使用。這兩個在接收後都可以利用 Pattern matching 的方式去做判斷,或是 Rust 有提供 is_some()
、is_none()
(for Option
) 和 is_ok()
、is_err()
(for Result
) 來讓你檢視。而 Result
也有 Method ok()
來將 Result
轉成是 Option
(Ok
=> Some
,Err
=> None
)Some
或 Ok
的時候取值,None
或 Err
的時候引發 Panic,可以直接拿 Option
或 Result
的 Instance 去呼叫 unwrap()
或 expect(<some message>)
來取代每次繁瑣的 Pattern matching (如果有另外的處理方式當然還是要寫),此外還有像是 unwrap_or()
(回傳一個預設值而不是 Panic)。